package com.crazymakercircle.imServer.distributed;

import com.crazymakercircle.constants.ServerConstants;
import com.crazymakercircle.entity.ImNode;
import com.crazymakercircle.util.JsonUtil;
import com.crazymakercircle.zk.CuratorZKclient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

import java.util.List;

/*
 * IM 节点的ZK协调客户端
 *
 **/
@Data
@Slf4j
public class ImWorker {

    //Zk curator 客户端
    private CuratorFramework client = null;

    //保存当前Znode节点的路径，创建后返回
    private String pathRegistered = null; //当前节点的注册路径

    private ImNode localNode = null; //本地节点信息

    private static ImWorker singleInstance = null;  //IM单例实例
    private boolean inited = false;  //是否已初始化

    //取得单例
    public synchronized static ImWorker getInst() {   //线程安全的 单例模式获取方法

        if (null == singleInstance) {

            singleInstance = new ImWorker();
            singleInstance.localNode = new ImNode();
        }
        return singleInstance;
    }

    private ImWorker() {   //私有构造器，防止外部直接创建实例

    }

    // 在zookeeper中创建临时节点
    /*
      检查是否已初始化，如果没有则创建，并初始化Zookeeper 客户端和本地节点
      删除父节点下没有子节点的节点 ，并确保父节点存在。
     */
    public synchronized void init() {
        /*如果已经初始化，则直接返回 */
        if (inited) {
            return;
        }
        inited = true;
        /*如果客户端为空， 则初始化客户端*/
        if (null == client) {
            this.client = CuratorZKclient.instance.getClient();
        }
        /*如果本地节点为空，则初始化本地节点*/
        if (null == localNode) {
            localNode = new ImNode();
        }
//        删除空父节点并确保父节点存在
        deleteWhenHasNoChildren(ServerConstants.MANAGE_PATH);
        /*确保父节点存在*/
        createParentIfNeeded(ServerConstants.MANAGE_PATH);

        // 创建一个 ZNode 节点
        // 节点的 payload 为当前worker 实例
        try {
          /*  将本地节点信息序列化为 JSON 字节流。
           。*/
            byte[] payload = JsonUtil.object2JsonBytes(localNode);
            /* 在 ZooKeeper 中创建一个临时顺序节点*/
            pathRegistered = client.create()
                    .creatingParentsIfNeeded()
                    .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                    .forPath(ServerConstants.PATH_PREFIX, payload);

            //为node 设置id
            localNode.setId(getId());
            log.info("本地节点, path={}, id={}", pathRegistered, localNode.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
/*设置本机 IP 和端口。*/
    public void setLocalNode(String ip, int port) {
        localNode.setHost(ip);
        localNode.setPort(port);
    }

    /**
     * 取得IM 节点编号
     *
     * @return 编号
     */
    public long getId() {
    /*根据注册路径获取节点 ID*/
        return getIdByPath(pathRegistered);

    }

    /*
     * 取得IM 节点编号
     *
     *用于解析给定的路径字符串，以获取路径末尾的节点编号
     *
     * 主要逻辑包括：
     *  * 1. 检查路径是否为null，以确保路径的有效性
     *  * 2. 使用lastIndexOf方法找到最后一个路径前缀的位置，以确定节点编号的起始位置
     *  * 3. 提取并验证节点编号字符串，如果提取失败或解析为long类型失败，则抛出运行时异常
     *  *
     *  * @param path 路径字符串，用于定位节点
     *  * @return 节点编号，以long类型返回
     *  * @throws RuntimeException 如果路径为null或节点ID获取失败，则抛出此异常
     */
    public long getIdByPath(String path) {
        String sid = null;//初始化节点编号字符串
        if (null == path) {
            throw new RuntimeException("节点路径有误");
        }
        //查找最后一个路径前缀的位置 ，以便获取节点编号
        int index = path.lastIndexOf(ServerConstants.PATH_PREFIX);
        if (index >= 0) {
            //计算节点编号字符串的起始位置
            index += ServerConstants.PATH_PREFIX.length();
            //提取节点编号字符串
            sid = index <= path.length() ? path.substring(index) : null;
        }
         /*检查节点编号字符串是否为null， 如果为null则抛出异常 */
        if (null == sid) {
            throw new RuntimeException("节点ID获取失败");
        }
        /*将节点编号字符串解析为long 并返回*/
        return Long.parseLong(sid);

    }


    /*
     * 增加负载，表示有用户登录成功
     *
     * @return 成功状态
     */
    public boolean incBalance() {
        /*检查节点是否已设置*/
        if (null == localNode) {
            throw new RuntimeException("还没有设置Node 节点");
        }
        // 增加负载：增加负载，并写回zookeeper
        while (true) {
            try {
                /*增加本地节点的负载*/
                localNode.incrementBalance();
                /*将更新后的节点信息转换为JSON格式的 字节数组*/
                byte[] payload = JsonUtil.object2JsonBytes(localNode);
                /*更新Zookeeper中注册路径的数据*/
                client.setData().forPath(pathRegistered, payload);
                return true;
            } catch (Exception e) {
                return false;
            }
        }

    }

    /*
     * 减少负载，表示有用户下线，写回zookeeper
     *
     * @return 成功状态
     */
    public boolean decrBalance() {
        if (null == localNode) {
            throw new RuntimeException("还没有设置Node 节点");
        }
        while (true) {
            try {
                /*递减负载，说明有用户下线， 并写回zookeeper*/
                localNode.decrementBalance();

                byte[] payload = JsonUtil.object2JsonBytes(localNode);
                /*使用zookeeper客户端更新指定路径下的节点数据*/
                client.setData().forPath(pathRegistered, payload);
                return true;
            } catch (Exception e) {
                return false;
            }
        }

    }

    /**
     * 创建父节点
     *
     * @param managePath 父节点路径
     */
    private void createParentIfNeeded(String managePath) {
      //检查 父节点是否存在
        try {
            Stat stat = client.checkExists().forPath(managePath);
            /*如果父节点不存在，则创建*/
            if (null == stat) {
                client.create()
                        .creatingParentsIfNeeded()
                        .withProtection()//设置节点的保护权限，只有拥有权限的用户可以访问
                        .withMode(CreateMode.PERSISTENT) //设置接单的创建模式为持久化节点，这种节点一旦创建就不会删除
                        .forPath(managePath);//指定要创建的节点路径
            }
        } catch (Exception e) {
            /*异常处理：打印异常信息*/
            e.printStackTrace();
        }

    }


    /**
     * 返回本地的节点信息
     *
     * @return 本地的节点信息
     */
    public ImNode getLocalNodeInfo() {
        return localNode;
    }


    //删除该路径
    public boolean delPath(String path) {
        boolean b = false;

        //检测是否存在该路径。
        try {
            Void stat = client.delete().forPath(path); // 尝试删除指定路径的节点
            b = stat == null ? false : true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return b;
    }

    //获取子节点
    public List<String> getChildren(String path) {

        //检测是否存在该路径。
        try {
            List<String> children = client.getChildren().forPath(path);
            return children;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /*当指定路径下的节点没有子节点时，删除该节点
    * @param path 要检查并可能删除的节点路径*/
    public void deleteWhenHasNoChildren(String path) {
        /*查找路径中最后一个斜杠的位置，用于后序提取父节点路径*/
        int index = path.lastIndexOf("/");
//        提取父节点的路径
        String parent = path.substring(0, index);

        boolean exist = isNodeExist(parent);//检查父节点是否存在
        if (exist) {
            /*获取父节点下的所有子节点*/
            List<String> children = getChildren(parent);
            /*如果子节点列表不为空 且 大小为0 ， 表示父节点为空节点*/
            if (null != children && children.size() == 0) {
                /*删除空的 父节点*/
                delPath(parent);
                /*记录删除操作的 日志信息*/
                log.info("删除空的 父节点:" + parent);

            }
        }
    }

    /**
     * 检查节点
     */
    public boolean isNodeExist(String zkPath) {
        try {

            Stat stat = client.checkExists().forPath(zkPath);
            if (null == stat) {
                log.info("节点不存在:", zkPath);
                return false;
            } else {

                log.info("节点存在 stat is:", stat.toString());
                return true;

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    static {
        //JVM关闭时的钩子函数
        Runtime.getRuntime().addShutdownHook(
                new Thread(() -> {
                    ImWorker.getInst().deleteNode();
                }, "关掉worker，删除zk节点"));
    }

    private void deleteNode() {
        log.info("删除 worker node, path={}, id={}", pathRegistered, localNode.getId());
        try {

            Stat stat = client.checkExists().forPath(pathRegistered);
            if (null == stat) {
                log.info("节点不存在:{}", pathRegistered);
            } else {

                client.delete().forPath(pathRegistered);

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}